home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / XMLLIB.PY < prev    next >
Encoding:
Python Source  |  2000-05-04  |  27.3 KB  |  748 lines

  1. # A parser for XML, using the derived class as static DTD.
  2. # Author: Sjoerd Mullender.
  3.  
  4. import re
  5. import string
  6.  
  7.  
  8. version = '0.1'
  9.  
  10. # Regular expressions used for parsing
  11.  
  12. _S = '[ \t\r\n]+'
  13. _opS = '[ \t\r\n]*'
  14. _Name = '[a-zA-Z_:][-a-zA-Z0-9._:]*'
  15. illegal = re.compile('[^\t\r\n -\176\240-\377]') # illegal chars in content
  16. interesting = re.compile('[]&<]')
  17.  
  18. amp = re.compile('&')
  19. ref = re.compile('&(' + _Name + '|#[0-9]+|#x[0-9a-fA-F]+)[^-a-zA-Z0-9._:]')
  20. entityref = re.compile('&(?P<name>' + _Name + ')[^-a-zA-Z0-9._:]')
  21. charref = re.compile('&#(?P<char>[0-9]+[^0-9]|x[0-9a-fA-F]+[^0-9a-fA-F])')
  22. space = re.compile(_S + '$')
  23. newline = re.compile('\n')
  24.  
  25. starttagopen = re.compile('<' + _Name)
  26. endtagopen = re.compile('</')
  27. starttagend = re.compile(_opS + '(?P<slash>/?)>')
  28. endbracket = re.compile(_opS + '>')
  29. tagfind = re.compile(_Name)
  30. cdataopen = re.compile(r'<!\[CDATA\[')
  31. cdataclose = re.compile(r'\]\]>')
  32. # this matches one of the following:
  33. # SYSTEM SystemLiteral
  34. # PUBLIC PubidLiteral SystemLiteral
  35. _SystemLiteral = '(?P<%s>\'[^\']*\'|"[^"]*")'
  36. _PublicLiteral = '(?P<%s>"[-\'()+,./:=?;!*#@$_%% \n\ra-zA-Z0-9]*"|' \
  37.                         "'[-()+,./:=?;!*#@$_%% \n\ra-zA-Z0-9]*')"
  38. _ExternalId = '(?:SYSTEM|' \
  39.                  'PUBLIC'+_S+_PublicLiteral%'pubid'+ \
  40.               ')'+_S+_SystemLiteral%'syslit'
  41. doctype = re.compile('<!DOCTYPE'+_S+'(?P<name>'+_Name+')'
  42.                      '(?:'+_S+_ExternalId+')?'+_opS)
  43. xmldecl = re.compile('<\?xml'+_S+
  44.                      'version'+_opS+'='+_opS+'(?P<version>\'[^\']*\'|"[^"]*")'+
  45.                      '(?:'+_S+'encoding'+_opS+'='+_opS+
  46.                         "(?P<encoding>'[A-Za-z][-A-Za-z0-9._]*'|"
  47.                         '"[A-Za-z][-A-Za-z0-9._]*"))?'
  48.                      '(?:'+_S+'standalone'+_opS+'='+_opS+
  49.                         '(?P<standalone>\'(?:yes|no)\'|"(?:yes|no)"))?'+
  50.                      _opS+'\?>')
  51. procopen = re.compile(r'<\?(?P<proc>' + _Name + ')' + _opS)
  52. procclose = re.compile(_opS + r'\?>')
  53. commentopen = re.compile('<!--')
  54. commentclose = re.compile('-->')
  55. doubledash = re.compile('--')
  56. attrfind = re.compile(
  57.     _S + '(?P<name>' + _Name + ')'
  58.     '(' + _opS + '=' + _opS +
  59.     '(?P<value>\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9.:+*%?!()_#=~]+))')
  60. attrtrans = string.maketrans(' \r\n\t', '    ')
  61.  
  62.  
  63. # XML parser base class -- find tags and call handler functions.
  64. # Usage: p = XMLParser(); p.feed(data); ...; p.close().
  65. # The dtd is defined by deriving a class which defines methods with
  66. # special names to handle tags: start_foo and end_foo to handle <foo>
  67. # and </foo>, respectively.  The data between tags is passed to the
  68. # parser by calling self.handle_data() with some data as argument (the
  69. # data may be split up in arbutrary chunks).  Entity references are
  70. # passed by calling self.handle_entityref() with the entity reference
  71. # as argument.
  72.  
  73. class XMLParser:
  74.  
  75.     # Interface -- initialize and reset this instance
  76.     def __init__(self, verbose=0):
  77.         self.verbose = verbose
  78.         self.reset()
  79.  
  80.     # Interface -- reset this instance.  Loses all unprocessed data
  81.     def reset(self):
  82.         self.rawdata = ''
  83.         self.stack = []
  84.         self.nomoretags = 0
  85.         self.literal = 0
  86.         self.lineno = 1
  87.         self.__at_start = 1
  88.         self.__seen_doctype = None
  89.         self.__seen_starttag = 0
  90.  
  91.     # For derived classes only -- enter literal mode (CDATA) till EOF
  92.     def setnomoretags(self):
  93.         self.nomoretags = self.literal = 1
  94.  
  95.     # For derived classes only -- enter literal mode (CDATA)
  96.     def setliteral(self, *args):
  97.         self.literal = 1
  98.  
  99.     # Interface -- feed some data to the parser.  Call this as
  100.     # often as you want, with as little or as much text as you
  101.     # want (may include '\n').  (This just saves the text, all the
  102.     # processing is done by goahead().)
  103.     def feed(self, data):
  104.         self.rawdata = self.rawdata + data
  105.         self.goahead(0)
  106.  
  107.     # Interface -- handle the remaining data
  108.     def close(self):
  109.         self.goahead(1)
  110.  
  111.     # Interface -- translate references
  112.     def translate_references(self, data, all = 1):
  113.         i = 0
  114.         while 1:
  115.             res = amp.search(data, i)
  116.             if res is None:
  117.                 return data
  118.             res = ref.match(data, res.start(0))
  119.             if res is None:
  120.                 self.syntax_error("bogus `&'")
  121.                 i =i+1
  122.                 continue
  123.             i = res.end(0)
  124.             if data[i - 1] != ';':
  125.                 self.syntax_error("`;' missing after entity/char reference")
  126.                 i = i-1
  127.             str = res.group(1)
  128.             pre = data[:res.start(0)]
  129.             post = data[i:]
  130.             if str[0] == '#':
  131.                 if str[1] == 'x':
  132.                     str = chr(string.atoi(str[2:], 16))
  133.                 else:
  134.                     str = chr(string.atoi(str[1:]))
  135.                 data = pre + str + post
  136.                 i = res.start(0)+len(str)
  137.             elif all:
  138.                 if self.entitydefs.has_key(str):
  139.                     data = pre + self.entitydefs[str] + post
  140.                     i = res.start(0)    # rescan substituted text
  141.                 else:
  142.                     self.syntax_error('reference to unknown entity')
  143.                     # can't do it, so keep the entity ref in
  144.                     data = pre + '&' + str + ';' + post
  145.                     i = res.start(0) + len(str) + 2
  146.             else:
  147.                 # just translating character references
  148.                 pass                    # i is already postioned correctly
  149.  
  150.     # Internal -- handle data as far as reasonable.  May leave state
  151.     # and data to be processed by a subsequent call.  If 'end' is
  152.     # true, force handling all data as if followed by EOF marker.
  153.     def goahead(self, end):
  154.         rawdata = self.rawdata
  155.         i = 0
  156.         n = len(rawdata)
  157.         while i < n:
  158.             if i > 0:
  159.                 self.__at_start = 0
  160.             if self.nomoretags:
  161.                 data = rawdata[i:n]
  162.                 self.handle_data(data)
  163.                 self.lineno = self.lineno + string.count(data, '\n')
  164.                 i = n
  165.                 break
  166.             res = interesting.search(rawdata, i)
  167.             if res:
  168.                     j = res.start(0)
  169.             else:
  170.                     j = n
  171.             if i < j:
  172.                 if self.__at_start:
  173.                     self.syntax_error('illegal data at start of file')
  174.                 self.__at_start = 0
  175.                 data = rawdata[i:j]
  176.                 if not self.stack and not space.match(data):
  177.                     self.syntax_error('data not in content')
  178.                 if illegal.search(data):
  179.                     self.syntax_error('illegal character in content')
  180.                 self.handle_data(data)
  181.                 self.lineno = self.lineno + string.count(data, '\n')
  182.             i = j
  183.             if i == n: break
  184.             if rawdata[i] == '<':
  185.                 if starttagopen.match(rawdata, i):
  186.                     if self.literal:
  187.                         data = rawdata[i]
  188.                         self.handle_data(data)
  189.                         self.lineno = self.lineno + string.count(data, '\n')
  190.                         i = i+1
  191.                         continue
  192.                     k = self.parse_starttag(i)
  193.                     if k < 0: break
  194.                     self.__seen_starttag = 1
  195.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  196.                     i = k
  197.                     continue
  198.                 if endtagopen.match(rawdata, i):
  199.                     k = self.parse_endtag(i)
  200.                     if k < 0: break
  201.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  202.                     i =  k
  203.                     self.literal = 0
  204.                     continue
  205.                 if commentopen.match(rawdata, i):
  206.                     if self.literal:
  207.                         data = rawdata[i]
  208.                         self.handle_data(data)
  209.                         self.lineno = self.lineno + string.count(data, '\n')
  210.                         i = i+1
  211.                         continue
  212.                     k = self.parse_comment(i)
  213.                     if k < 0: break
  214.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  215.                     i = k
  216.                     continue
  217.                 if cdataopen.match(rawdata, i):
  218.                     k = self.parse_cdata(i)
  219.                     if k < 0: break
  220.                     self.lineno = self.lineno + string.count(rawdata[i:i], '\n')
  221.                     i = k
  222.                     continue
  223.                 res = xmldecl.match(rawdata, i)
  224.                 if res:
  225.                     if not self.__at_start:
  226.                         self.syntax_error("<?xml?> declaration not at start of document")
  227.                     version, encoding, standalone = res.group('version',
  228.                                                               'encoding',
  229.                                                               'standalone')
  230.                     if version[1:-1] != '1.0':
  231.                         raise RuntimeError, 'only XML version 1.0 supported'
  232.                     if encoding: encoding = encoding[1:-1]
  233.                     if standalone: standalone = standalone[1:-1]
  234.                     self.handle_xml(encoding, standalone)
  235.                     i = res.end(0)
  236.                     continue
  237.                 res = procopen.match(rawdata, i)
  238.                 if res:
  239.                     k = self.parse_proc(i)
  240.                     if k < 0: break
  241.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  242.                     i = k
  243.                     continue
  244.                 res = doctype.match(rawdata, i)
  245.                 if res:
  246.                     if self.literal:
  247.                         data = rawdata[i]
  248.                         self.handle_data(data)
  249.                         self.lineno = self.lineno + string.count(data, '\n')
  250.                         i = i+1
  251.                         continue
  252.                     if self.__seen_doctype:
  253.                         self.syntax_error('multiple DOCTYPE elements')
  254.                     if self.__seen_starttag:
  255.                         self.syntax_error('DOCTYPE not at beginning of document')
  256.                     k = self.parse_doctype(res)
  257.                     if k < 0: break
  258.                     self.__seen_doctype = res.group('name')
  259.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  260.                     i = k
  261.                     continue
  262.             elif rawdata[i] == '&':
  263.                 res = charref.match(rawdata, i)
  264.                 if res is not None:
  265.                     i = res.end(0)
  266.                     if rawdata[i-1] != ';':
  267.                         self.syntax_error("`;' missing in charref")
  268.                         i = i-1
  269.                     if not self.stack:
  270.                         self.syntax_error('data not in content')
  271.                     self.handle_charref(res.group('char')[:-1])
  272.                     self.lineno = self.lineno + string.count(res.group(0), '\n')
  273.                     continue
  274.                 res = entityref.match(rawdata, i)
  275.                 if res is not None:
  276.                     i = res.end(0)
  277.                     if rawdata[i-1] != ';':
  278.                         self.syntax_error("`;' missing in entityref")
  279.                         i = i-1
  280.                     name = res.group('name')
  281.                     if self.entitydefs.has_key(name):
  282.                         self.rawdata = rawdata = rawdata[:res.start(0)] + self.entitydefs[name] + rawdata[i:]
  283.                         n = len(rawdata)
  284.                         i = res.start(0)
  285.                     else:
  286.                         self.syntax_error('reference to unknown entity')
  287.                         self.unknown_entityref(name)
  288.                     self.lineno = self.lineno + string.count(res.group(0), '\n')
  289.                     continue
  290.             elif rawdata[i] == ']':
  291.                 if n-i < 3:
  292.                     break
  293.                 if cdataclose.match(rawdata, i):
  294.                     self.syntax_error("bogus `]]>'")
  295.                 self.handle_data(rawdata[i])
  296.                 i = i+1
  297.                 continue
  298.             else:
  299.                 raise RuntimeError, 'neither < nor & ??'
  300.             # We get here only if incomplete matches but
  301.             # nothing else
  302.             break
  303.         # end while
  304.         if i > 0:
  305.             self.__at_start = 0
  306.         if end and i < n:
  307.             data = rawdata[i]
  308.             self.syntax_error("bogus `%s'" % data)
  309.             if illegal.search(data):
  310.                 self.syntax_error('illegal character in content')
  311.             self.handle_data(data)
  312.             self.lineno = self.lineno + string.count(data, '\n')
  313.             self.rawdata = rawdata[i+1:]
  314.             return self.goahead(end)
  315.         self.rawdata = rawdata[i:]
  316.         if end:
  317.             if not self.__seen_starttag:
  318.                 self.syntax_error('no elements in file')
  319.             if self.stack:
  320.                 self.syntax_error('missing end tags')
  321.                 while self.stack:
  322.                     self.finish_endtag(self.stack[-1])
  323.  
  324.     # Internal -- parse comment, return length or -1 if not terminated
  325.     def parse_comment(self, i):
  326.         rawdata = self.rawdata
  327.         if rawdata[i:i+4] <> '<!--':
  328.             raise RuntimeError, 'unexpected call to handle_comment'
  329.         res = commentclose.search(rawdata, i+4)
  330.         if not res:
  331.             return -1
  332.         if doubledash.search(rawdata, i+4, res.start(0)):
  333.             self.syntax_error("`--' inside comment")
  334.         if rawdata[res.start(0)-1] == '-':
  335.             self.syntax_error('comment cannot end in three dashes')
  336.         if illegal.search(rawdata, i+4, res.start(0)):
  337.             self.syntax_error('illegal character in comment')
  338.         self.handle_comment(rawdata[i+4: res.start(0)])
  339.         return res.end(0)
  340.  
  341.     # Internal -- handle DOCTYPE tag, return length or -1 if not terminated
  342.     def parse_doctype(self, res):
  343.         rawdata = self.rawdata
  344.         n = len(rawdata)
  345.         name = res.group('name')
  346.         pubid, syslit = res.group('pubid', 'syslit')
  347.         if pubid is not None:
  348.             pubid = pubid[1:-1]         # remove quotes
  349.             pubid = string.join(string.split(pubid)) # normalize
  350.         if syslit is not None: syslit = syslit[1:-1] # remove quotes
  351.         j = k = res.end(0)
  352.         if k >= n:
  353.             return -1
  354.         if rawdata[k] == '[':
  355.             level = 0
  356.             k = k+1
  357.             dq = sq = 0
  358.             while k < n:
  359.                 c = rawdata[k]
  360.                 if not sq and c == '"':
  361.                     dq = not dq
  362.                 elif not dq and c == "'":
  363.                     sq = not sq
  364.                 elif sq or dq:
  365.                     pass
  366.                 elif level <= 0 and c == ']':
  367.                     res = endbracket.match(rawdata, k+1)
  368.                     if not res:
  369.                         return -1
  370.                     self.handle_doctype(name, pubid, syslit, rawdata[j+1:k])
  371.                     return res.end(0)
  372.                 elif c == '<':
  373.                     level = level + 1
  374.                 elif c == '>':
  375.                     level = level - 1
  376.                     if level < 0:
  377.                         self.syntax_error("bogus `>' in DOCTYPE")
  378.                 k = k+1
  379.         res = endbracket.search(rawdata, k)
  380.         if not res:
  381.             return -1
  382.         if res.start(0) != k:
  383.             self.syntax_error('garbage in DOCTYPE')
  384.         self.handle_doctype(name, pubid, syslit, None)
  385.         return res.end(0)
  386.  
  387.     # Internal -- handle CDATA tag, return length or -1 if not terminated
  388.     def parse_cdata(self, i):
  389.         rawdata = self.rawdata
  390.         if rawdata[i:i+9] <> '<![CDATA[':
  391.             raise RuntimeError, 'unexpected call to parse_cdata'
  392.         res = cdataclose.search(rawdata, i+9)
  393.         if not res:
  394.             return -1
  395.         if illegal.search(rawdata, i+9, res.start(0)):
  396.             self.syntax_error('illegal character in CDATA')
  397.         if not self.stack:
  398.             self.syntax_error('CDATA not in content')
  399.         self.handle_cdata(rawdata[i+9:res.start(0)])
  400.         return res.end(0)
  401.  
  402.     __xml_attributes = {'version': '1.0', 'standalone': 'no', 'encoding': None}
  403.     # Internal -- handle a processing instruction tag
  404.     def parse_proc(self, i):
  405.         rawdata = self.rawdata
  406.         end = procclose.search(rawdata, i)
  407.         if not end:
  408.             return -1
  409.         j = end.start(0)
  410.         if illegal.search(rawdata, i+2, j):
  411.             self.syntax_error('illegal character in processing instruction')
  412.         res = tagfind.match(rawdata, i+2)
  413.         if not res:
  414.             raise RuntimeError, 'unexpected call to parse_proc'
  415.         k = res.end(0)
  416.         name = res.group(0)
  417.         if string.find(string.lower(name), 'xml') >= 0:
  418.             self.syntax_error('illegal processing instruction target name')
  419.         self.handle_proc(name, rawdata[k:j])
  420.         return end.end(0)
  421.  
  422.     # Internal -- parse attributes between i and j
  423.     def parse_attributes(self, tag, k, j, attributes = None):
  424.         rawdata = self.rawdata
  425.         # Now parse the data between k and j into a tag and attrs
  426.         attrdict = {}
  427.         try:
  428.             # convert attributes list to dictionary
  429.             d = {}
  430.             for a in attributes:
  431.                 d[a] = None
  432.             attributes = d
  433.         except TypeError:
  434.             pass
  435.         while k < j:
  436.             res = attrfind.match(rawdata, k)
  437.             if not res: break
  438.             attrname, attrvalue = res.group('name', 'value')
  439.             if attrvalue is None:
  440.                 self.syntax_error('no attribute value specified')
  441.                 attrvalue = attrname
  442.             elif attrvalue[:1] == "'" == attrvalue[-1:] or \
  443.                  attrvalue[:1] == '"' == attrvalue[-1:]:
  444.                 attrvalue = attrvalue[1:-1]
  445.             else:
  446.                 self.syntax_error('attribute value not quoted')
  447.             if attributes is not None and not attributes.has_key(attrname):
  448.                 self.syntax_error('unknown attribute %s of element %s' %
  449.                                   (attrname, tag))
  450.             if attrdict.has_key(attrname):
  451.                 self.syntax_error('attribute specified twice')
  452.             attrvalue = string.translate(attrvalue, attrtrans)
  453.             attrdict[attrname] = self.translate_references(attrvalue)
  454.             k = res.end(0)
  455.         if attributes is not None:
  456.             # fill in with default attributes
  457.             for key, val in attributes.items():
  458.                 if val is not None and not attrdict.has_key(key):
  459.                     attrdict[key] = val
  460.         return attrdict, k
  461.  
  462.     # Internal -- handle starttag, return length or -1 if not terminated
  463.     def parse_starttag(self, i):
  464.         rawdata = self.rawdata
  465.         # i points to start of tag
  466.         end = endbracket.search(rawdata, i+1)
  467.         if not end:
  468.             return -1
  469.         j = end.start(0)
  470.         res = tagfind.match(rawdata, i+1)
  471.         if not res:
  472.             raise RuntimeError, 'unexpected call to parse_starttag'
  473.         k = res.end(0)
  474.         tag = res.group(0)
  475.         if not self.__seen_starttag and self.__seen_doctype:
  476.             if tag != self.__seen_doctype:
  477.                 self.syntax_error('starttag does not match DOCTYPE')
  478.         if self.__seen_starttag and not self.stack:
  479.             self.syntax_error('multiple elements on top level')
  480.         if hasattr(self, tag + '_attributes'):
  481.             attributes = getattr(self, tag + '_attributes')
  482.         else:
  483.             attributes = None
  484.         attrdict, k = self.parse_attributes(tag, k, j, attributes)
  485.         res = starttagend.match(rawdata, k)
  486.         if not res:
  487.             self.syntax_error('garbage in start tag')
  488.         self.finish_starttag(tag, attrdict)
  489.         if res and res.group('slash') == '/':
  490.             self.finish_endtag(tag)
  491.         return end.end(0)
  492.  
  493.     # Internal -- parse endtag
  494.     def parse_endtag(self, i):
  495.         rawdata = self.rawdata
  496.         end = endbracket.search(rawdata, i+1)
  497.         if not end:
  498.             return -1
  499.         res = tagfind.match(rawdata, i+2)
  500.         if not res:
  501.             self.syntax_error('no name specified in end tag')
  502.             tag = ''
  503.             k = i+2
  504.         else:
  505.             tag = res.group(0)
  506.             k = res.end(0)
  507.         if k != end.start(0):
  508.             self.syntax_error('garbage in end tag')
  509.         self.finish_endtag(tag)
  510.         return end.end(0)
  511.  
  512.     # Internal -- finish processing of start tag
  513.     # Return -1 for unknown tag, 1 for balanced tag
  514.     def finish_starttag(self, tag, attrs):
  515.         self.stack.append(tag)
  516.         methodname = 'start_' + tag
  517.         if hasattr(self, methodname):
  518.             method = getattr(self, methodname)
  519.             self.handle_starttag(tag, method, attrs)
  520.             return 1
  521.         else:
  522.             self.unknown_starttag(tag, attrs)
  523.             return -1
  524.  
  525.     # Internal -- finish processing of end tag
  526.     def finish_endtag(self, tag):
  527.         methodname = 'end_' + tag
  528.         if not tag:
  529.             self.syntax_error('name-less end tag')
  530.             found = len(self.stack) - 1
  531.             if found < 0:
  532.                 self.unknown_endtag(tag)
  533.                 return
  534.         else:
  535.             if tag not in self.stack:
  536.                 self.syntax_error('unopened end tag')
  537.                 if hasattr(self, methodname):
  538.                     method = getattr(self, methodname)
  539.                     self.handle_endtag(tag, method)
  540.                 else:
  541.                     self.unknown_endtag(tag)
  542.                 return
  543.             found = len(self.stack)
  544.             for i in range(found):
  545.                 if self.stack[i] == tag:
  546.                     found = i
  547.         while len(self.stack) > found:
  548.             if found < len(self.stack) - 1:
  549.                 self.syntax_error('missing close tag for %s' % self.stack[-1])
  550.             tag = self.stack[-1]
  551.             if hasattr(self, methodname):
  552.                 method = getattr(self, methodname)
  553.                 self.handle_endtag(tag, method)
  554.             else:
  555.                 self.unknown_endtag(tag)
  556.             del self.stack[-1]
  557.  
  558.     # Overridable -- handle xml processing instruction
  559.     def handle_xml(self, encoding, standalone):
  560.         pass
  561.  
  562.     # Overridable -- handle DOCTYPE
  563.     def handle_doctype(self, tag, pubid, syslit, data):
  564.         pass
  565.  
  566.     # Overridable -- handle start tag
  567.     def handle_starttag(self, tag, method, attrs):
  568.         method(attrs)
  569.  
  570.     # Overridable -- handle end tag
  571.     def handle_endtag(self, tag, method):
  572.         method()
  573.  
  574.     # Example -- handle character reference, no need to override
  575.     def handle_charref(self, name):
  576.         try:
  577.             if name[0] == 'x':
  578.                 n = string.atoi(name[1:], 16)
  579.             else:
  580.                 n = string.atoi(name)
  581.         except string.atoi_error:
  582.             self.unknown_charref(name)
  583.             return
  584.         if not 0 <= n <= 255:
  585.             self.unknown_charref(name)
  586.             return
  587.         self.handle_data(chr(n))
  588.  
  589.     # Definition of entities -- derived classes may override
  590.     entitydefs = {'lt': '<',        # must use charref
  591.                   'gt': '>',
  592.                   'amp': '&',       # must use charref
  593.                   'quot': '"',
  594.                   'apos': ''',
  595.                   }
  596.  
  597.     # Example -- handle entity reference, no need to override
  598.     def handle_entityref(self, name):
  599.         table = self.entitydefs
  600.         if table.has_key(name):
  601.             self.handle_data(table[name])
  602.         else:
  603.             self.unknown_entityref(name)
  604.             return
  605.  
  606.     # Example -- handle data, should be overridden
  607.     def handle_data(self, data):
  608.         pass
  609.  
  610.     # Example -- handle cdata, could be overridden
  611.     def handle_cdata(self, data):
  612.         pass
  613.  
  614.     # Example -- handle comment, could be overridden
  615.     def handle_comment(self, data):
  616.         pass
  617.  
  618.     # Example -- handle processing instructions, could be overridden
  619.     def handle_proc(self, name, data):
  620.         pass
  621.  
  622.     # Example -- handle relatively harmless syntax errors, could be overridden
  623.     def syntax_error(self, message):
  624.         raise RuntimeError, 'Syntax error at line %d: %s' % (self.lineno, message)
  625.  
  626.     # To be overridden -- handlers for unknown objects
  627.     def unknown_starttag(self, tag, attrs): pass
  628.     def unknown_endtag(self, tag): pass
  629.     def unknown_charref(self, ref): pass
  630.     def unknown_entityref(self, ref): pass
  631.  
  632.  
  633. class TestXMLParser(XMLParser):
  634.  
  635.     def __init__(self, verbose=0):
  636.         self.testdata = ""
  637.         XMLParser.__init__(self, verbose)
  638.  
  639.     def handle_xml(self, encoding, standalone):
  640.         self.flush()
  641.         print 'xml: encoding =',encoding,'standalone =',standalone
  642.  
  643.     def handle_doctype(self, tag, pubid, syslit, data):
  644.         self.flush()
  645.         print 'DOCTYPE:',tag, `data`
  646.  
  647.     def handle_entity(self, name, strval, pubid, syslit, ndata):
  648.         self.flush()
  649.         print 'ENTITY:',`data`
  650.  
  651.     def handle_data(self, data):
  652.         self.testdata = self.testdata + data
  653.         if len(`self.testdata`) >= 70:
  654.             self.flush()
  655.  
  656.     def flush(self):
  657.         data = self.testdata
  658.         if data:
  659.             self.testdata = ""
  660.             print 'data:', `data`
  661.  
  662.     def handle_cdata(self, data):
  663.         self.flush()
  664.         print 'cdata:', `data`
  665.  
  666.     def handle_proc(self, name, data):
  667.         self.flush()
  668.         print 'processing:',name,`data`
  669.  
  670.     def handle_comment(self, data):
  671.         self.flush()
  672.         r = `data`
  673.         if len(r) > 68:
  674.             r = r[:32] + '...' + r[-32:]
  675.         print 'comment:', r
  676.  
  677.     def syntax_error(self, message):
  678.         print 'error at line %d:' % self.lineno, message
  679.  
  680.     def unknown_starttag(self, tag, attrs):
  681.         self.flush()
  682.         if not attrs:
  683.             print 'start tag: <' + tag + '>'
  684.         else:
  685.             print 'start tag: <' + tag,
  686.             for name, value in attrs.items():
  687.                 print name + '=' + '"' + value + '"',
  688.             print '>'
  689.  
  690.     def unknown_endtag(self, tag):
  691.         self.flush()
  692.         print 'end tag: </' + tag + '>'
  693.  
  694.     def unknown_entityref(self, ref):
  695.         self.flush()
  696.         print '*** unknown entity ref: &' + ref + ';'
  697.  
  698.     def unknown_charref(self, ref):
  699.         self.flush()
  700.         print '*** unknown char ref: &#' + ref + ';'
  701.  
  702.     def close(self):
  703.         XMLParser.close(self)
  704.         self.flush()
  705.  
  706. def test(args = None):
  707.     import sys
  708.  
  709.     if not args:
  710.         args = sys.argv[1:]
  711.  
  712.     if args and args[0] == '-s':
  713.         args = args[1:]
  714.         klass = XMLParser
  715.     else:
  716.         klass = TestXMLParser
  717.  
  718.     if args:
  719.         file = args[0]
  720.     else:
  721.         file = 'test.xml'
  722.  
  723.     if file == '-':
  724.         f = sys.stdin
  725.     else:
  726.         try:
  727.             f = open(file, 'r')
  728.         except IOError, msg:
  729.             print file, ":", msg
  730.             sys.exit(1)
  731.  
  732.     data = f.read()
  733.     if f is not sys.stdin:
  734.         f.close()
  735.  
  736.     x = klass()
  737.     try:
  738.         for c in data:
  739.             x.feed(c)
  740.         x.close()
  741.     except RuntimeError, msg:
  742.         print msg
  743.         sys.exit(1)
  744.  
  745.  
  746. if __name__ == '__main__':
  747.     test()
  748.